//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Includes
#include <mattise.h>
#include <stdint.h>
#include <mem/util.h>
#include <mem/heap.h>
#include <kernio/io.h>
#include <process/common.h>

// Functions

// common process init code
struct pentry* kCommonProcessInit( uint32_t pagedir, uint32_t flags )
{
    // verify that the process table is valid
    if( ptable == 0 )
        return 0;

    // iterate to the end of the process list
    struct pentry* ent, *prev;
    int32_t id = 0;
    for( ent = ptable; ent != 0; prev = ent, ent = ent->next, id++ );

    // check for validity (ent should be 0, prev should not be)
    if( prev == 0 )
        return 0;
    if( ent != 0 )
        return 0; // now's a great time to start debugging

    // now we have a valid pointer to the end of the list in prev,
    // create a new context and define it as a process
    struct context* newcntx = (struct context*) malloc( sizeof( struct context ) );

    // check for a valid new context
    if( newcntx == 0 )
        return 0;

    // get a new process table entry
    struct pentry* newentry = (struct pentry*) malloc( sizeof( struct pentry ) );

    // check for a valid entry
    if( newentry == 0 )
    {
        free( newcntx );
        return 0;
    }

#if DEBUG
    dprintf( "process newcntx = %x, newentry = %x, prev = %x\n", newcntx, newentry, prev );
#endif

    // set up the new context (this is simply the process, the thread is made later)
    newcntx->id = lastpid++;
    newcntx->isprocess = 1;
    newcntx->cr3 = pagedir;
    newcntx->parentid = -1;
	newcntx->numchildren = 1; // required to be 1 at startup
	newcntx->ent = newentry;

    // link in the entry
    newentry->prev = prev;
    newentry->me = newcntx;
    newentry->next = 0;
    prev->next = newentry;

#if DEBUG
	dprintf( "linked into list [entry=%x]\n", newentry );
#endif
	
	// allocate space for at least one child id and save it
	newcntx->childpids = (uint32_t*) malloc( sizeof( uint32_t ) );

	// setup the stack bitmaps
	if(flags & TF_USPACE)
		SetUpContextBitmap(newcntx);
	else
		newcntx->user_stack_bitmap = 0;

	// return the new entry
	return newentry;
}

// creates a new process that must run an executable file rather than a specific address
int32_t kCreateProcessFromEXE( const char* exename, uint32_t flags, uint32_t pagedir, uint32_t prio, uint32_t* childid, char** argv )
{
    // find out if we should re-enable interrupts later
    uint32_t __START_EFLAGS = 0;
    asm volatile( "pushf; popl %0" : "=r" (__START_EFLAGS) );

    // we can't be interrupted here
    asm volatile( "cli" );

	// process pointers
	struct pentry* newentry = kCommonProcessInit( pagedir, flags );
	if(newentry == 0)
	{
		// we can be interrupted again, but only enable interrupts if they were on before
		if( __START_EFLAGS & 0x200 )
			asm volatile( "sti" );
		return -1;
	}
	struct context* newcntx = newentry->me;

	// create the child thread, if needed
    if( exename != 0 )
        newcntx->childid = kCreateThread( newcntx->id, 0, flags, prio, argv, newentry, exename );
    else
        newcntx->childid = -1; // no children yet

	// set the child id pointer
	if( childid )
		*childid = newcntx->childid;

#if DEBUG
	dprintf( "enabling ints\n" );
#endif

    // we can be interrupted again, but only enable interrupts if they were on before
    if( __START_EFLAGS & 0x200 )
        asm volatile( "sti" );

    // return our id
    return newcntx->id;
}

// creates a new process
int32_t kCreateProcess( uint32_t startaddr, uint32_t flags, uint32_t pagedir, uint32_t prio, uint32_t* childid, char** argv )
{
    // find out if we should re-enable interrupts later
    uint32_t __START_EFLAGS = 0;
    asm volatile( "pushf; popl %0" : "=r" (__START_EFLAGS) );

    // we can't be interrupted here
    asm volatile( "cli" );

	// process pointers
	struct pentry* newentry = kCommonProcessInit( pagedir, flags );
	if(newentry == 0)
	{
		// we can be interrupted again, but only enable interrupts if they were on before
		if( __START_EFLAGS & 0x200 )
			asm volatile( "sti" );
		return -1;
	}
	struct context* newcntx = newentry->me;

	// create the child thread, if needed
    if( startaddr != 0 )
        newcntx->childid = kCreateThread( newcntx->id, startaddr, flags, prio, argv, newentry, 0 );
    else
        newcntx->childid = -1; // no children yet

	// set the child id pointer
	if( childid )
		*childid = newcntx->childid;

#if DEBUG
	dprintf( "enabling ints\n" );
#endif

    // we can be interrupted again, but only enable interrupts if they were on before
    if( __START_EFLAGS & 0x200 )
        asm volatile( "sti" );

    // return our id
    return newcntx->id;
}
